package de.zainodis.triplog.data.share;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.util.Log;

import com.j256.ormlite.misc.TransactionManager;
import com.j256.ormlite.support.ConnectionSource;

import java.sql.SQLException;
import java.util.concurrent.Callable;

import de.zainodis.triplog.data.DiaryEntry;
import de.zainodis.triplog.data.LocationEntry;
import de.zainodis.triplog.data.TripEntry;
import de.zainodis.triplog.data.WeatherEntry;
import de.zainodis.triplog.data.persistence.DatabaseHelper;
import de.zainodis.triplog.data.persistence.HelperProvider;
import de.zainodis.triplog.data.persistence.IDiaryDao;
import de.zainodis.triplog.data.persistence.ILocationDao;
import de.zainodis.triplog.data.persistence.ITripDao;
import de.zainodis.triplog.data.persistence.IWeatherDao;

/**
 * Created by fzarrai on 8/20/2014.
 */
//TODO rename class to TriplogContentProvider
public class WeatherProvider extends ContentProvider {

    private static final String TAG = "TripLogContentProvider";

    private static final UriMatcher sUriMatcher = TriplogContract.buildUriMatcher();
    //Used to access the database
    private DatabaseHelper openHelper;


    private static final SQLiteQueryBuilder sWeatherByLocationSettingQueryBuilder;


    static {
        //TODO this is possibly not needed anymore.
        sWeatherByLocationSettingQueryBuilder = new SQLiteQueryBuilder();
        sWeatherByLocationSettingQueryBuilder.setTables(
                IWeatherDao.TABLE_NAME + " INNER JOIN " +
                        ILocationDao.TABLE_NAME +
                        " ON " + IWeatherDao.TABLE_NAME +
                        "." + IWeatherDao.COLUMN_LOC_KEY +
                        " = " + ILocationDao.TABLE_NAME +
                        "." + ILocationDao.COLUMN_ID
        );
    }


    //Generally useful select statement, so we don't have to write them all over again, if we need them more than once
    public static final String WEATHER_FORECAST_SELECTION = IWeatherDao.COLUMN_FK_LOCATION_ID + " = ? AND " + IWeatherDao.COLUMN_DATETEXT + " >= ? ";

    private static final String LOCATION_SETTING_SELECTION = ILocationDao.TABLE_NAME + "." + ILocationDao.COLUMN_LOCATION_SETTING + " = ? ";

    private static final String LOCATION_SETTING_WITH_START_DATE_SELECTION = ILocationDao.TABLE_NAME + "." + ILocationDao.COLUMN_LOCATION_SETTING + " = ? AND " + IWeatherDao.COLUMN_DATETEXT + " >= ? ";

    private static final String LOCATION_SETTING_AND_DAY_SELECTION = ILocationDao.TABLE_NAME + "." + ILocationDao.COLUMN_LOCATION_SETTING + " = ? AND " + IWeatherDao.COLUMN_DATETEXT + " = ? ";

    /**Use this to query for all closed trips - as an argument pass TripEntry's STRING_DEFAULT_VALUE */
    public static final String TRIPS_BY_END_DATE_SELECTION = ITripDao.TABLE_NAME + "." + ITripDao.COLUMN_DATE_END + " != ? ";

    private Cursor getWeatherByLocationSetting(Uri uri, String[] projection, String sortOrder) {
        String locationSetting = TriplogContract.getLocationSettingFromUri(uri);
        String startDate = TriplogContract.getStartDateFromUri(uri);
        String[] selectionArgs;
        String selection;
        if (startDate == null) {
            selection = LOCATION_SETTING_SELECTION;
            selectionArgs = new String[]{locationSetting};
        } else {
            selectionArgs = new String[]{locationSetting, startDate};
            selection = LOCATION_SETTING_WITH_START_DATE_SELECTION;
        }
        return sWeatherByLocationSettingQueryBuilder.query(openHelper.getReadableDatabase(),
                projection,
                selection,
                selectionArgs,
                null,
                null,
                sortOrder
        );
    }

    private Cursor getWeatherByLocationSettingAndDate(
            Uri uri, String[] projection, String sortOrder) {
        String locationSetting = TriplogContract.getLocationSettingFromUri(uri);
        String date = TriplogContract.getDateFromUri(uri);
        return sWeatherByLocationSettingQueryBuilder.query(openHelper.getReadableDatabase(),
                projection,
                LOCATION_SETTING_AND_DAY_SELECTION,
                new String[]{locationSetting, date},
                null,
                null,
                sortOrder
        );
    }

    @Override
    public boolean onCreate() {
        openHelper = new DatabaseHelper(getContext());
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder) {
        // Here's the switch statement that, given a URI, will determine what kind of request it is,
        // and query the database accordingly.
        Cursor retCursor;
        switch (sUriMatcher.match(uri)) {
            case TriplogContract.WEATHER_WITH_LOCATION_AND_DATE: {
                retCursor = getWeatherByLocationSettingAndDate(uri, projection, sortOrder);
                break;
            }
            case TriplogContract.WEATHER_WITH_LOCATION: {
                retCursor = getWeatherByLocationSetting(uri, projection, sortOrder);
                break;
            }
            case TriplogContract.WEATHER: {
                retCursor = openHelper.getReadableDatabase().query(
                        IWeatherDao.TABLE_NAME,
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null,
                        sortOrder
                );
                break;
            }
            case TriplogContract.WEATHER_ID: {
                retCursor = openHelper.getReadableDatabase().query(
                        IWeatherDao.TABLE_NAME,
                        projection,
                        IWeatherDao.COLUMN_ID + " = ?",
                        new String[]{String.valueOf(ContentUris.parseId(uri))},
                        null,
                        null,
                        sortOrder
                );
                break;
            }
            case TriplogContract.LOCATION_ID: {
                retCursor = openHelper.getReadableDatabase().query(
                        ILocationDao.TABLE_NAME,
                        projection,
                        ILocationDao.COLUMN_ID + " = ?",
                        new String[]{String.valueOf(ContentUris.parseId(uri))},
                        null,
                        null,
                        sortOrder
                );
                break;
            }
            // "location"
            case TriplogContract.LOCATION: {
                retCursor = openHelper.getReadableDatabase().query(
                        ILocationDao.TABLE_NAME,
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null,
                        sortOrder
                );
                break;
            }
            case TriplogContract.TRIP: {
                retCursor = openHelper.getReadableDatabase().query(
                        ITripDao.TABLE_NAME,
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null,
                        sortOrder
                );
                break;
            }
            case TriplogContract.TRIP_ID: {
                retCursor = openHelper.getReadableDatabase().query(
                        ITripDao.TABLE_NAME,
                        projection,
                        ITripDao.COLUMN_ID + " = ?",
                        new String[]{String.valueOf(ContentUris.parseId(uri))},
                        null,
                        null,
                        sortOrder
                );
                break;
            }
            case TriplogContract.DIARY: {
                retCursor = openHelper.getReadableDatabase().query(
                        IDiaryDao.TABLE_NAME,
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null,
                        sortOrder
                );
                break;
            }
            case TriplogContract.DIARY_ID: {
                retCursor = openHelper.getReadableDatabase().query(
                        IDiaryDao.TABLE_NAME,
                        projection,
                        IDiaryDao.COLUMN_ID + " = ?",
                        new String[]{String.valueOf(ContentUris.parseId(uri))},
                        null,
                        null,
                        sortOrder
                );
                break;
            }
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
        retCursor.setNotificationUri(getContext().getContentResolver(), uri);
        return retCursor;
    }

    @Override
    public String getType(Uri uri) {
        // Use the Uri Matcher to determine what kind of URI this is.
        final int match = sUriMatcher.match(uri);
        switch (match) {
            case TriplogContract.WEATHER_WITH_LOCATION_AND_DATE:
                return TriplogContract.CONTENT_ITEM_TYPE_WEATHER;

            case TriplogContract.WEATHER_WITH_LOCATION:
                return TriplogContract.CONTENT_TYPE_WEATHER;

            case TriplogContract.WEATHER:
                return TriplogContract.CONTENT_TYPE_WEATHER;

            case TriplogContract.WEATHER_ID:
                return TriplogContract.CONTENT_ITEM_TYPE_WEATHER;

            case TriplogContract.LOCATION:
                return TriplogContract.CONTENT_TYPE_LOCATION;

            case TriplogContract.LOCATION_ID:
                return TriplogContract.CONTENT_ITEM_TYPE_LOCATION;

            case TriplogContract.TRIP:
                return TriplogContract.CONTENT_TYPE_TRIP;

            case TriplogContract.TRIP_ID:
                return TriplogContract.CONTENT_ITEM_TYPE_TRIP;

            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        final SQLiteDatabase db = openHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        Uri returnUri;
        switch (match) {
            case TriplogContract.WEATHER: {
                HelperProvider helper = new HelperProvider();
                DatabaseHelper dbHelper = helper.getHelper(getContext());

                long id = -1;
                try {
                    id = dbHelper.getWeatherDao().createIfNotExists(new WeatherEntry(values)).getId();
                } catch (SQLException e) {
                    Log.e(TAG, "Failed to insert weather into database.", e);
                } finally {
                    helper.releaseHelper();
                }
                if (id > 0) {
                    returnUri = TriplogContract.buildWeatherUri(id);
                } else {
                    throw new android.database.SQLException("Failed to insert row into " + uri);
                }
                break;
            }
            case TriplogContract.LOCATION: {
                HelperProvider helper = new HelperProvider();
                DatabaseHelper dbHelper = helper.getHelper(getContext());

                long id = -1;
                try {
                    id = dbHelper.getLocationDao().createIfNotExists(new LocationEntry(values)).getId();
                } catch (SQLException e) {
                    Log.e(TAG, "Failed to insert location into database.", e);
                } finally {
                    helper.releaseHelper();
                }
                if (id > 0) {
                    returnUri = TriplogContract.buildLocationUri(id);
                } else {
                    throw new android.database.SQLException("Failed to insert row into " + uri);
                }
                break;
            }
            case TriplogContract.TRIP: {

                HelperProvider helper = new HelperProvider();
                DatabaseHelper dbHelper = helper.getHelper(getContext());

                long id = -1;
                try {
                    id = dbHelper.getTripDao().createIfNotExists(getContext(), new TripEntry(values));
                } catch (SQLException e) {
                    Log.e(TAG, "Failed to insert trip into database.", e);
                } finally {
                    helper.releaseHelper();
                }
                if (id > 0) {
                    returnUri = TriplogContract.buildTripUri(id);
                } else {
                    throw new android.database.SQLException("Failed to insert row into " + uri);
                }
                break;
            }
            case TriplogContract.DIARY: {
                HelperProvider helper = new HelperProvider();
                DatabaseHelper dbHelper = helper.getHelper(getContext());

                long id = -1;
                try {
                    id = dbHelper.getDiaryDao().createIfNotExists(getContext(), new DiaryEntry(values));
                } catch (SQLException e) {
                    Log.e(TAG, "Failed to insert diary into database.", e);
                } finally {
                    helper.releaseHelper();
                }
                if (id > 0) {
                    returnUri = TriplogContract.buildDiaryUri(id);
                } else {
                    throw new android.database.SQLException("Failed to insert row into " + uri);
                }
                break;
            }
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return returnUri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        final int match = sUriMatcher.match(uri);
        int rowsDeleted = 0;
        switch (match) {
            case TriplogContract.WEATHER:
                //Since weather doesn't have child elements that need to be deleted as well, we can just pass on the request
                rowsDeleted = openHelper.getWritableDatabase().delete(IWeatherDao.TABLE_NAME, selection, selectionArgs);
                break;
            case TriplogContract.LOCATION_ID:
                //Delete location based on it's id
                long locationId = ContentUris.parseId(uri);
                try {
                    if (locationId > 0) {
                        rowsDeleted = openHelper.getTripDao().deleteById(locationId);
                        //TODO currently not supporting nested transaction
                        //Delete associated weather entries
                        getContext().getContentResolver().delete(TriplogContract.CONTENT_URI_WEATHER, IWeatherDao.COLUMN_FK_LOCATION_ID + " = ?",
                                new String[]{String.valueOf(locationId)});
                    } else {
                        Log.e(TAG, "No valid id provided for deleting location.");
                        return -1;

                    }

                } catch (SQLException e) {

                }
                break;
            case TriplogContract.TRIP_ID:
                //Extract the id first
                long tripId = ContentUris.parseId(uri);
                if (tripId > 0) {
                    rowsDeleted = deleteTripAsTransaction(tripId, selection, selectionArgs);
                } else {
                    Log.e(TAG, "No valid id provided for deleting trip.");
                    return -1;
                }
                break;
            case TriplogContract.DIARY:
                rowsDeleted = openHelper.getWritableDatabase().delete(IDiaryDao.TABLE_NAME, selection, selectionArgs);
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
        // Because a null deletes all rows
        if (selection == null || rowsDeleted != 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return rowsDeleted;

    }

    /**
     * Deletes a trip and all associated data in one transaction.
     *
     * @param tripId        the trip to delete.
     * @param selection     select columns for the deletion query.
     * @param selectionArgs select arguments for the deletion query.
     * @return number of rows deleted; this is either 1 on success, 0 if there is no matching trip.
     */
    private int deleteTripAsTransaction(final long tripId, final String selection, final String[] selectionArgs) {
        int count = runAsTransaction(openHelper.getConnectionSource(), new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                //TODO might have to modify arguments to use trip id
                int rowsDeleted = openHelper.getWritableDatabase().delete(ITripDao.TABLE_NAME, selection, selectionArgs);

                if (rowsDeleted > 0) {
                    //Delete associated location, first check if there are any
                    Cursor cursor = getContext().getContentResolver().query(
                            TriplogContract.CONTENT_URI_LOCATION, new String[]{ITripDao.COLUMN_ID}, ILocationDao.COLUMN_FK_TRIP + " = ?",
                            new String[]{String.valueOf(tripId)},
                            null);
                    if (cursor.moveToFirst()) {
                        //Delete associated location
                        int index = cursor.getColumnIndex(ITripDao.COLUMN_ID);
                        long locationId = cursor.getLong(index);

                        //Delete the location, the delete query will take care of deleting associated weather entries
                        getContext().getContentResolver().delete(TriplogContract.buildLocationUri(locationId), ILocationDao.COLUMN_ID + " = ?",
                                new String[]{String.valueOf(locationId)});

                    }
                    //Delete related diary entries
                    getContext().getContentResolver().delete(TriplogContract.CONTENT_URI_DIARY, IDiaryDao.COLUMN_FK_TRIP_ID + " = ?",
                            new String[]{String.valueOf(tripId)});

                    //TODO delete related photos
                }
                return rowsDeleted;
            }
        });
        return count;
    }

    @Override
    public int update(
            Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        final SQLiteDatabase db = openHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        int rowsUpdated;
        //This should be called via the domain model objects, so updating of related objects is handled automatically
        switch (match) {
            case TriplogContract.WEATHER:
                rowsUpdated = db.update(IWeatherDao.TABLE_NAME, values, selection,
                        selectionArgs);
                break;
            case TriplogContract.LOCATION:
                rowsUpdated = db.update(ILocationDao.TABLE_NAME, values, selection,
                        selectionArgs);
                break;
            case TriplogContract.TRIP:
                rowsUpdated = db.update(ITripDao.TABLE_NAME, values, selection,
                        selectionArgs);
                break;
            case TriplogContract.DIARY:
                rowsUpdated = db.update(IDiaryDao.TABLE_NAME, values, selection,
                        selectionArgs);
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
        if (rowsUpdated > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return rowsUpdated;
    }

    @Override
    public int bulkInsert(final Uri uri, final ContentValues[] values) {
        final int match = sUriMatcher.match(uri);
        switch (match) {
            //Bulk insert currently only makes sense for weather as we update it frequently
            case TriplogContract.WEATHER:
                //Optimize bulk insert
                int count = runAsTransaction(openHelper.getConnectionSource(), new Callable<Integer>() {
                    @Override
                    public Integer call() throws Exception {
                        int returnCount = 0;
                        for (ContentValues value : values) {
                            WeatherEntry entry = new WeatherEntry(value);
                            int rowsChanged;
                            //Check if an entry with the same date exists
                            IWeatherDao dao = openHelper.getWeatherDao();
                            long id = dao.queryForMatchingDate(entry, getContext());
                            if (id > 0) {
                                entry.setId(id);
                                //Entry already exists, so do an update
                                rowsChanged = openHelper.getWeatherDao().update(entry);
                            } else {
                                //Entry has no id, means its new, so do a create
                                rowsChanged = openHelper.getWeatherDao().create(entry);
                            }
                            returnCount += rowsChanged;
                        }
                        return returnCount;

                    }
                });
                if (count > 0) {
                    getContext().getContentResolver().notifyChange(uri, null);
                }
                return count;
        }
        //we only support bulk
        return -1;
    }

    /**
     * @param source
     * @param transaction the implemented transaction.
     * @return the number of transaction parts that were executed; -1 if the transaction failed.
     */

    private int runAsTransaction(ConnectionSource source, Callable<Integer> transaction) {

        try {
            return TransactionManager.callInTransaction(source, transaction);
        } catch (SQLException e) {
            Log.e(TAG, "Transaction failed.", e);
            return -1;
        }
    }
}
